﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media.Media3D;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Media;
using System.Xaml;
using System.IO;
using System.Windows.Media.Imaging;
using Eniux.Windows.ThreeDEngine.Geometry;

namespace Eniux.Windows.ThreeDEngine.Helper
{
    /// <summary>
    /// 3D视窗帮助类
    /// </summary>
    public static class Viewport3DHelper
    {
        #region 公开的方法

        /// <summary>
        /// 将指定的3D视窗的内容以图片方式保存在剪贴板
        /// </summary>
        /// <param name="view">3D视窗</param>
        /// <param name="m"></param>
        public static void Copy(Viewport3D view, int m = 1)
        {
            Clipboard.SetImage(RenderBitmap(view, Brushes.White, m));
        }

        /// <summary>
        /// 将指定的3D视窗的内容以图片方式保存在剪贴板
        /// </summary>
        /// <param name="view">3D视窗</param>
        /// <param name="width">宽度</param>
        /// <param name="height">高度</param>
        /// <param name="background">背景</param>
        /// <param name="m"></param>
        public static void Copy(Viewport3D view, double width, double height, Brush background, int m = 1)
        {
            Clipboard.SetImage(RenderBitmap(view, width, height, background));
        }

        /// <summary>
        /// 将指定的3D视窗内容以xaml文本格式存在剪贴板
        /// </summary>
        /// <param name="view">3D视窗</param>
        public static void CopyXaml(Viewport3D view)
        {
            //Clipboard.SetText(XamlWriter.Save(view));
        }

        /// <summary>
        /// 以某种格式导出3D视窗内容
        /// </summary>
        /// <param name="view">3D视窗</param>
        /// <param name="fileName">要保存的文件名</param>
        /// <param name="background">背景</param>
        public static void Export(Viewport3D view, string fileName, Brush background = null)
        {
            string ext = Path.GetExtension(fileName).ToLower();
            switch (ext)
            {
                case ".jpg":
                case ".png":
                    SaveBitmap(view, fileName, background, 2);
                    break;
                case ".xaml":
                    ExportXaml(view, fileName);
                    break;
                case ".xml":
                    ExportKerkythea(view, fileName, background);
                    break;
                case ".obj":
                    ExportObj(view, fileName);
                    break;
                case ".x3d":
                    ExportX3d(view, fileName);
                    break;
                case ".dae":
                    ExportCollada(view, fileName);
                    break;
                default:
                    throw new InvalidOperationException("Not supported file format.");
            }
        }

        /// <summary>
        /// 查找3D视窗的2D位置的碰撞检测结果
        /// </summary>
        /// <param name="viewport">3D视窗</param>
        /// <param name="position">2D点位置</param>
        /// <returns>碰撞检测结果</returns>
        public static List<HitResult> FindHits(Viewport3D viewport, Point position)
        {
            var camera = viewport.Camera as ProjectionCamera;
            if (camera == null)
            {
                return null;
            }

            var result = new List<HitResult>();
            HitTestResultCallback callback = hit =>
            {
                var rayHit = hit as RayMeshGeometry3DHitTestResult;
                if (rayHit != null)
                {
                    if (rayHit.MeshHit != null)
                    {
                        var p = GetGlobalHitPosition(rayHit, viewport);
                        var nn = GetNormalHit(rayHit);
                        var n = nn.HasValue ? nn.Value : new Vector3D(0, 0, 1);

                        result.Add(
                            new HitResult
                            {
                                Distance = (camera.Position - p).Length,
                                RayHit = rayHit,
                                Normal = n,
                                Position = p
                            });
                    }
                }

                return HitTestResultBehavior.Continue;
            };

            var hitParams = new PointHitTestParameters(position);
            VisualTreeHelper.HitTest(viewport, null, callback, hitParams);

            result.OrderBy(k => k.Distance);
            return result;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="viewport">3D视窗</param>
        /// <param name="position">2D碰撞点</param>
        /// <param name="point">3D点</param>
        /// <param name="normal"></param>
        /// <param name="visual"></param>
        /// <returns></returns>
        public static bool FindNearest(
            Viewport3D viewport, Point position, out Point3D point, out Vector3D normal, out DependencyObject visual)
        {
            var camera = viewport.Camera as ProjectionCamera;
            if (camera == null)
            {
                point = new Point3D();
                normal = new Vector3D();
                visual = null;
                return false;
            }

            var hitParams = new PointHitTestParameters(position);

            double minimumDistance = double.MaxValue;
            var nearestPoint = new Point3D();
            var nearestNormal = new Vector3D();
            DependencyObject nearestObject = null;

            VisualTreeHelper.HitTest(
                viewport,
                null,
                delegate(HitTestResult hit)
                {
                    var rayHit = hit as RayMeshGeometry3DHitTestResult;
                    if (rayHit != null)
                    {
                        var mesh = rayHit.MeshHit;
                        if (mesh != null)
                        {
                            var p1 = mesh.Positions[rayHit.VertexIndex1];
                            var p2 = mesh.Positions[rayHit.VertexIndex2];
                            var p3 = mesh.Positions[rayHit.VertexIndex3];
                            double x = p1.X * rayHit.VertexWeight1 + p2.X * rayHit.VertexWeight2
                                       + p3.X * rayHit.VertexWeight3;
                            double y = p1.Y * rayHit.VertexWeight1 + p2.Y * rayHit.VertexWeight2
                                       + p3.Y * rayHit.VertexWeight3;
                            double z = p1.Z * rayHit.VertexWeight1 + p2.Z * rayHit.VertexWeight2
                                       + p3.Z * rayHit.VertexWeight3;

                            // point in local coordinates
                            var p = new Point3D(x, y, z);

                            // transform to global coordinates

                            // first transform the Model3D hierarchy
                            var t2 = GetTransform(rayHit.VisualHit, rayHit.ModelHit);
                            if (t2 != null)
                            {
                                p = t2.Transform(p);
                            }

                            // then transform the Visual3D hierarchy up to the Viewport3D ancestor
                            var t = GetTransform(viewport, rayHit.VisualHit);
                            if (t != null)
                            {
                                p = t.Transform(p);
                            }

                            double distance = (camera.Position - p).LengthSquared;
                            if (distance < minimumDistance)
                            {
                                minimumDistance = distance;
                                nearestPoint = p;
                                nearestNormal = Vector3D.CrossProduct(p2 - p1, p3 - p1);
                                nearestObject = hit.VisualHit;
                            }
                        }
                    }

                    return HitTestResultBehavior.Continue;
                },
                hitParams);

            point = nearestPoint;
            visual = nearestObject;
            normal = nearestNormal;

            if (minimumDistance == double.MaxValue)
            {
                return false;
            }

            normal.Normalize();
            return true;
        }

        /// <summary>
        /// 查找给定2D点在3D视窗中最近的3D点
        /// </summary>
        /// <param name="viewport">3D视窗</param>
        /// <param name="position">2D点</param>
        /// <returns></returns>
        public static Point3D? FindNearestPoint(Viewport3D viewport, Point position)
        {
            Point3D p;
            Vector3D n;
            DependencyObject obj;
            if (FindNearest(viewport, position, out p, out n, out obj))
            {
                return p;
            }

            return null;
        }

        /// <summary>
        /// 查找在3D视窗中离给定2D点最近的Visual3D对象
        /// </summary>
        /// <param name="viewport">3D视窗</param>
        /// <param name="position">2D点</param>
        /// <returns></returns>
        public static Visual3D FindNearestVisual(Viewport3D viewport, Point position)
        {
            Point3D p;
            Vector3D n;
            DependencyObject obj;
            if (FindNearest(viewport, position, out p, out n, out obj))
            {
                return obj as Visual3D;
            }

            return null;
        }

        /// <summary>
        /// 获取相机的三维转换
        /// </summary>
        /// <param name="vis"></param>
        /// <returns></returns>
        public static Matrix3D GetCameraTransform(Viewport3DVisual vis)
        {
            return GetTotalTransform(vis.Camera, vis.Viewport.Size.Width / vis.Viewport.Size.Height);
        }

        /// <summary>
        /// 获取相机的三维转换
        /// </summary>
        /// <param name="viewport">3D视窗</param>
        /// <returns></returns>
        public static Matrix3D GetCameraTransform(Viewport3D viewport)
        {
            return GetTotalTransform(viewport.Camera, viewport.ActualWidth / viewport.ActualHeight);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="cam"></param>
        /// <param name="aspectRatio"></param>
        /// <returns></returns>
        public static Matrix3D GetInverseTransform(Camera cam, double aspectRatio)
        {
            var m = GetTotalTransform(cam, aspectRatio);

            if (!m.HasInverse)
            {
                throw new ApplicationException("camera transform has no inverse");
            }

            m.Invert();
            return m;
        }

        /// <summary>
        /// 获取3D视窗的所有灯光
        /// </summary>
        /// <param name="viewport">3D视窗</param>
        /// <returns></returns>
        public static Light[] GetLights(Viewport3D viewport)
        {
            var models = SearchFor<Light>(viewport.Children);
            return models.Select(m => m as Light).ToArray();
        }

        /// <summary>
        /// Projection matrix, page 327-331
        /// </summary>
        /// <param name="camera">
        /// </param>
        /// <param name="aspectRatio">
        /// </param>
        /// <returns>
        /// </returns>
        public static Matrix3D GetProjectionMatrix(Camera camera, double aspectRatio)
        {
            if (camera == null)
            {
                throw new ArgumentNullException("camera");
            }

            if (camera is MatrixCamera)
            {
                return (camera as MatrixCamera).ProjectionMatrix;
            }

            if (camera is OrthographicCamera)
            {
                var orthocam = camera as OrthographicCamera;

                double xScale = 2 / orthocam.Width;
                double yScale = xScale * aspectRatio;
                double zNear = orthocam.NearPlaneDistance;
                double zFar = orthocam.FarPlaneDistance;

                // Hey, check this out!
                if (double.IsPositiveInfinity(zFar))
                {
                    zFar = 1E10;
                }

                return new Matrix3D(
                    xScale, 0, 0, 0, 0, yScale, 0, 0, 0, 0, 1 / (zNear - zFar), 0, 0, 0, zNear / (zNear - zFar), 1);
            }

            if (camera is PerspectiveCamera)
            {
                var perscam = camera as PerspectiveCamera;

                // The angle-to-radian formula is a little off because only
                // half the angle enters the calculation.
                double xScale = 1 / Math.Tan(Math.PI * perscam.FieldOfView / 360);
                double yScale = xScale * aspectRatio;
                double zNear = perscam.NearPlaneDistance;
                double zFar = perscam.FarPlaneDistance;
                double zScale = zFar == double.PositiveInfinity ? -1 : (zFar / (zNear - zFar));
                double zOffset = zNear * zScale;

                return new Matrix3D(xScale, 0, 0, 0, 0, yScale, 0, 0, 0, 0, zScale, -1, 0, 0, zOffset, 0);
            }

            throw new ApplicationException("unknown camera type");
        }

        /// <summary>
        /// Get the ray into the view volume given by the position in 2D (screen coordinates)
        /// </summary>
        /// <param name="viewport">
        /// The viewport.
        /// </param>
        /// <param name="position">
        /// A 2D point.
        /// </param>
        /// <returns>
        /// A ray
        /// </returns>
        public static Ray3D GetRay(Viewport3D viewport, Point position)
        {
            Point3D point1, point2;
            bool ok = Point2DtoPoint3D(viewport, position, out point1, out point2);
            if (!ok)
            {
                return null;
            }

            return new Ray3D { Origin = point1, Direction = point2 - point1 };
        }

        /// <summary>
        /// Get the combined view and projection transform
        /// </summary>
        /// <param name="camera">
        /// </param>
        /// <param name="aspectRatio">
        /// </param>
        /// <returns>
        /// </returns>
        public static Matrix3D GetTotalTransform(Camera camera, double aspectRatio)
        {
            var m = Matrix3D.Identity;

            if (camera == null)
            {
                throw new ArgumentNullException("camera");
            }

            if (camera.Transform != null)
            {
                var cameraTransform = camera.Transform.Value;

                if (!cameraTransform.HasInverse)
                {
                    throw new ApplicationException("camera transform has no inverse");
                }

                cameraTransform.Invert();
                m.Append(cameraTransform);
            }

            m.Append(GetViewMatrix(camera));
            m.Append(GetProjectionMatrix(camera, aspectRatio));
            return m;
        }

        /// <summary>
        /// Gets the total transform.
        /// </summary>
        /// <param name="vis">
        /// The vis.
        /// </param>
        /// <returns>
        /// </returns>
        public static Matrix3D GetTotalTransform(Viewport3DVisual vis)
        {
            var m = GetCameraTransform(vis);
            m.Append(GetViewportTransform(vis));
            return m;
        }

        /// <summary>
        /// Gets the total transform.
        /// </summary>
        /// <param name="viewport">
        /// The viewport.
        /// </param>
        /// <returns>
        /// </returns>
        public static Matrix3D GetTotalTransform(Viewport3D viewport)
        {
            var matx = GetCameraTransform(viewport);
            matx.Append(GetViewportTransform(viewport));
            return matx;
        }

        /// <summary>
        /// Get the total transform of a Visual3D
        /// </summary>
        /// <param name="viewport">
        /// </param>
        /// <param name="visual">
        /// </param>
        /// <returns>
        /// </returns>
        public static GeneralTransform3D GetTransform(Viewport3D viewport, Visual3D visual)
        {
            if (visual == null)
            {
                return null;
            }

            foreach (var ancestor in viewport.Children)
            {
                if (visual.IsDescendantOf(ancestor))
                {
                    var g = new GeneralTransform3DGroup();

                    // this includes the visual.Transform
                    var ta = visual.TransformToAncestor(ancestor);
                    if (ta != null)
                    {
                        g.Children.Add(ta);
                    }

                    // add the transform of the top-level ancestor
                    g.Children.Add(ancestor.Transform);

                    return g;
                }
            }

            return visual.Transform;
        }

        /// <summary>
        /// Gets the transform from the specified Visual3D to the Model3D.
        /// </summary>
        /// <param name="visual">
        /// The source visual.
        /// </param>
        /// <param name="model">
        /// The target model.
        /// </param>
        /// <returns>
        /// </returns>
        public static GeneralTransform3D GetTransform(Visual3D visual, Model3D model)
        {
            var mv = visual as ModelVisual3D;
            if (mv != null)
            {
                return GetTransform(mv.Content, model, Transform3D.Identity);
            }

            return null;
        }

        /// <summary>
        /// Obtains the view transform matrix for a camera. (see page 327)
        /// </summary>
        /// <param name="camera">
        /// Camera to obtain the ViewMatrix for
        /// </param>
        /// <returns>
        /// A Matrix3D object with the camera view transform matrix, or a Matrix3D with all zeros if the "camera" is null.
        /// </returns>
        /// <exception cref="ApplicationException">
        /// if the 'camera' is neither of type MatrixCamera nor ProjectionCamera. 
        /// </exception>
        public static Matrix3D GetViewMatrix(Camera camera)
        {
            if (camera == null)
            {
                throw new ArgumentNullException("camera");
            }

            if (camera is MatrixCamera)
            {
                return (camera as MatrixCamera).ViewMatrix;
            }

            if (camera is ProjectionCamera)
            {
                // Reflector on: ProjectionCamera.CreateViewMatrix
                var projcam = camera as ProjectionCamera;

                var zAxis = -projcam.LookDirection;
                zAxis.Normalize();

                var xAxis = Vector3D.CrossProduct(projcam.UpDirection, zAxis);
                xAxis.Normalize();

                var yAxis = Vector3D.CrossProduct(zAxis, xAxis);
                var pos = (Vector3D)projcam.Position;

                return new Matrix3D(
                    xAxis.X,
                    yAxis.X,
                    zAxis.X,
                    0,
                    xAxis.Y,
                    yAxis.Y,
                    zAxis.Y,
                    0,
                    xAxis.Z,
                    yAxis.Z,
                    zAxis.Z,
                    0,
                    -Vector3D.DotProduct(xAxis, pos),
                    -Vector3D.DotProduct(yAxis, pos),
                    -Vector3D.DotProduct(zAxis, pos),
                    1);
            }

            throw new ApplicationException("unknown camera type");
        }

        /// <summary>
        /// Gets the viewport for the specified visual.
        /// </summary>
        /// <param name="visual">
        /// The visual.
        /// </param>
        /// <returns>
        /// </returns>
        public static Viewport3D GetViewport(Visual3D visual)
        {
            DependencyObject parent = visual;
            while (parent != null)
            {
                var vp = parent as Viewport3DVisual;
                if (vp != null)
                {
                    return vp.Parent as Viewport3D;
                }

                parent = VisualTreeHelper.GetParent(parent);
            }

            return null;
        }

        /// <summary>
        /// Gets the viewport transform.
        /// </summary>
        /// <param name="vis">
        /// The vis.
        /// </param>
        /// <returns>
        /// </returns>
        public static Matrix3D GetViewportTransform(Viewport3DVisual vis)
        {
            return new Matrix3D(
                vis.Viewport.Width / 2,
                0,
                0,
                0,
                0,
                -vis.Viewport.Height / 2,
                0,
                0,
                0,
                0,
                1,
                0,
                vis.Viewport.X + vis.Viewport.Width / 2,
                vis.Viewport.Y + vis.Viewport.Height / 2,
                0,
                1);
        }

        /// <summary>
        /// Gets the viewport transform.
        /// </summary>
        /// <param name="viewport">
        /// The viewport.
        /// </param>
        /// <returns>
        /// </returns>
        public static Matrix3D GetViewportTransform(Viewport3D viewport)
        {
            return new Matrix3D(
                viewport.ActualWidth / 2,
                0,
                0,
                0,
                0,
                -viewport.ActualHeight / 2,
                0,
                0,
                0,
                0,
                1,
                0,
                viewport.ActualWidth / 2,
                viewport.ActualHeight / 2,
                0,
                1);
        }

        /// <summary>
        /// Transforms a Point2D to a Point3D.
        /// </summary>
        /// <param name="viewport">
        /// The viewport.
        /// </param>
        /// <param name="ptIn">
        /// The pt in.
        /// </param>
        /// <param name="pointNear">
        /// The point near.
        /// </param>
        /// <param name="pointFar">
        /// The point far.
        /// </param>
        /// <returns>
        /// The point 2 dto point 3 d.
        /// </returns>
        public static bool Point2DtoPoint3D(
            Viewport3D viewport, Point ptIn, out Point3D pointNear, out Point3D pointFar)
        {
            pointNear = new Point3D();
            pointFar = new Point3D();

            var pointIn = new Point3D(ptIn.X, ptIn.Y, 0);
            var matrixViewport = GetViewportTransform(viewport);
            var matrixCamera = GetCameraTransform(viewport);

            if (!matrixViewport.HasInverse)
            {
                return false;
            }

            if (!matrixCamera.HasInverse)
            {
                return false;
            }

            matrixViewport.Invert();
            matrixCamera.Invert();

            var pointNormalized = matrixViewport.Transform(pointIn);
            pointNormalized.Z = 0.01;
            pointNear = matrixCamera.Transform(pointNormalized);
            pointNormalized.Z = 0.99;
            pointFar = matrixCamera.Transform(pointNormalized);

            return true;
        }

        /// <summary>
        /// Transforms a 2D point to a ray.
        /// </summary>
        /// <param name="viewport">
        /// The viewport.
        /// </param>
        /// <param name="ptIn">
        /// The pt in.
        /// </param>
        /// <returns>
        /// </returns>
        public static Ray3D Point2DtoRay3D(Viewport3D viewport, Point ptIn)
        {
            Point3D pointNear, pointFar;
            if (!Point2DtoPoint3D(viewport, ptIn, out pointNear, out pointFar))
            {
                return null;
            }

            return new Ray3D(pointNear, pointFar);
        }

        /// <summary>
        /// Transforms the Point3D to a Point2D.
        /// </summary>
        /// <param name="viewport">
        /// The viewport.
        /// </param>
        /// <param name="point">
        /// The point.
        /// </param>
        /// <returns>
        /// </returns>
        public static Point Point3DtoPoint2D(Viewport3D viewport, Point3D point)
        {
            var matrix = GetTotalTransform(viewport);
            var pointTransformed = matrix.Transform(point);
            var pt = new Point(pointTransformed.X, pointTransformed.Y);
            return pt;
        }

        /// <summary>
        /// Prints the specified viewport.
        /// </summary>
        /// <param name="vp">
        /// The viewport.
        /// </param>
        /// <param name="description">
        /// The description.
        /// </param>
        public static void Print(Viewport3D vp, string description)
        {
            var dlg = new PrintDialog();
            if (dlg.ShowDialog().GetValueOrDefault())
            {
                dlg.PrintVisual(vp, description);
            }
        }

        /// <summary>
        /// Renders the viewport to a bitmap.
        /// </summary>
        /// <param name="view">The viewport.</param>
        /// <param name="background">The background.</param>
        /// <param name="m">The oversampling multiplier.</param>
        /// <returns>A bitmap.</returns>
        public static BitmapSource RenderBitmap(Viewport3D view, Brush background, int m = 1)
        {
            var target = new WriteableBitmap((int)view.ActualWidth * m, (int)view.ActualHeight * m, 96, 96, PixelFormats.Pbgra32, null);

            var originalCamera = view.Camera;
            var vm = GetViewMatrix(originalCamera);
            double ar = view.ActualWidth / view.ActualHeight;

            for (int i = 0; i < m; i++)
            {
                for (int j = 0; j < m; j++)
                {
                    // change the camera viewport and scaling
                    var pm = GetProjectionMatrix(originalCamera, ar);
                    if (originalCamera is OrthographicCamera)
                    {
                        pm.OffsetX = m - 1 - i * 2;
                        pm.OffsetY = -(m - 1 - j * 2);
                    }
                    if (originalCamera is PerspectiveCamera)
                    {
                        pm.M31 = -(m - 1 - i * 2);
                        pm.M32 = m - 1 - j * 2;
                    }
                    pm.M11 *= m;
                    pm.M22 *= m;

                    var mc = new MatrixCamera(vm, pm);
                    view.Camera = mc;

                    var partialBitmap = new RenderTargetBitmap((int)view.ActualWidth, (int)view.ActualHeight, 96, 96, PixelFormats.Pbgra32);

                    // render background
                    var backgroundRectangle = new System.Windows.Shapes.Rectangle { Width = partialBitmap.Width, Height = partialBitmap.Height, Fill = background };
                    backgroundRectangle.Arrange(new Rect(0, 0, backgroundRectangle.Width, backgroundRectangle.Height));
                    partialBitmap.Render(backgroundRectangle);

                    // render 3d
                    partialBitmap.Render(view);

                    // copy to the target bitmap
                    CopyBitmap(partialBitmap, target, (int)(i * view.ActualWidth), (int)(j * view.ActualHeight));
                }
            }

            // restore the camera
            view.Camera = originalCamera;
            return target;
        }

        private static void CopyBitmap(BitmapSource source, WriteableBitmap target, int offsetx, int offsety)
        {
            // Calculate stride of source
            int stride = source.PixelWidth * (source.Format.BitsPerPixel / 8);

            // Create data array to hold source pixel data
            byte[] data = new byte[stride * source.PixelHeight];

            // Copy source image pixels to the data array
            source.CopyPixels(data, stride, 0);

            // Write the pixel data to the WriteableBitmap.
            target.WritePixels(new Int32Rect(offsetx, offsety, source.PixelWidth, source.PixelHeight), data, stride, 0);
        }

        /// <summary>
        /// Renders the viewport to a bitmap.
        /// </summary>
        /// <param name="view">The viewport.</param>
        /// <param name="width">The width.</param>
        /// <param name="height">The height.</param>
        /// <param name="background">The background.</param>
        /// <param name="m">The oversampling multiplier.</param>
        /// <returns>A bitmap.</returns>
        public static BitmapSource RenderBitmap(Viewport3D view, double width, double height, Brush background, int m = 1)
        {
            double w = view.Width;
            double h = view.Height;
            ResizeAndArrange(view, width, height);
            var rtb = RenderBitmap(view, background, m);
            ResizeAndArrange(view, w, h);
            return rtb;
        }

        /// <summary>
        /// Resizes and arranges the viewport.
        /// </summary>
        /// <param name="view">
        /// The view.
        /// </param>
        /// <param name="width">
        /// The width.
        /// </param>
        /// <param name="height">
        /// The height.
        /// </param>
        public static void ResizeAndArrange(Viewport3D view, double width, double height)
        {
            view.Width = width;
            view.Height = height;
            if (double.IsNaN(width) || double.IsNaN(height))
            {
                return;
            }

            view.Measure(new Size(width, height));
            view.Arrange(new Rect(0, 0, width, height));
        }

        /// <summary>
        /// Saves the viewport to a file.
        /// </summary>
        /// <param name="view">The view.</param>
        /// <param name="fileName">Name of the file.</param>
        /// <param name="background">The background brush.</param>
        /// <param name="m">The oversampling multiplier.</param>
        public static void SaveBitmap(Viewport3D view, string fileName, Brush background = null, int m = 1)
        {
            if (background == null)
                background = Brushes.Transparent;

            var bmp = RenderBitmap(view, background, m);
            BitmapEncoder encoder;
            string ext = Path.GetExtension(fileName);
            switch (ext.ToLower())
            {
                case ".jpg":
                    var jpg = new JpegBitmapEncoder();
                    jpg.Frames.Add(BitmapFrame.Create(bmp));
                    encoder = jpg;
                    break;
                case ".png":
                    var png = new PngBitmapEncoder();
                    png.Frames.Add(BitmapFrame.Create(bmp));
                    encoder = png;
                    break;
                default:
                    throw new InvalidOperationException("Not supported file format.");
            }

            using (Stream stm = File.Create(fileName))
            {
                encoder.Save(stm);
            }
        }

        /// <summary>
        /// Recursive search in a collection for objects of given type T
        /// </summary>
        /// <typeparam name="T">
        /// </typeparam>
        /// <param name="collection">
        /// </param>
        /// <returns>
        /// </returns>
        public static List<Model3D> SearchFor<T>(IEnumerable<Visual3D> collection)
        {
            var output = new List<Model3D>();
            SearchFor(collection, typeof(T), output);
            return output;
        }

        /// <summary>
        /// Unproject a point from the screen (2D) to a point on plane (3D)
        /// </summary>
        /// <param name="viewport">
        /// The viewport.
        /// </param>
        /// <param name="p">
        /// The 2D point.
        /// </param>
        /// <param name="position">
        /// plane position
        /// </param>
        /// <param name="normal">
        /// plane normal
        /// </param>
        /// <returns>
        /// A 3D point.
        /// </returns>
        /// <remarks>
        /// Map window coordinates to object coordinates like gluUnProject.
        /// </remarks>
        public static Point3D? UnProject(Viewport3D viewport, Point p, Point3D position, Vector3D normal)
        {
            Ray3D ray = GetRay(viewport, p);
            if (ray == null)
            {
                return null;
            }

            return ray.PlaneIntersection(position, normal);
        }

        /// <summary>
        /// Unproject a point from the screen (2D) to a point on the plane trough the camera target point.
        /// </summary>
        /// <param name="viewport">
        /// The viewport.
        /// </param>
        /// <param name="p">
        /// The 2D point.
        /// </param>
        /// <returns>
        /// A 3D point.
        /// </returns>
        public static Point3D? UnProject(Viewport3D viewport, Point p)
        {
            var pc = viewport.Camera as ProjectionCamera;
            if (pc == null)
            {
                return null;
            }

            return UnProject(viewport, p, pc.Position + pc.LookDirection, pc.LookDirection);
        }

        /// <summary>
        /// 获取3D视窗中所有的三角形
        /// </summary>
        /// <param name="viewport">3D视窗</param>
        /// <returns>三角形个数</returns>
        public static int GetTotalNumberOfTriangles(Viewport3D viewport)
        {
            int count = 0;
            Visual3DHelper.Traverse<GeometryModel3D>(viewport.Children, (m, t) => count += ((MeshGeometry3D)m.Geometry).TriangleIndices.Count / 3);
            return count;
        }

        #endregion

        #region 私有方法

        #region 导出文件方法

        /// <summary>
        /// Exports to kerkythea.
        /// </summary>
        /// <param name="view">The view.</param>
        /// <param name="fileName">Name of the file.</param>
        /// <param name="background">The background.</param>
        private static void ExportKerkythea(Viewport3D view, string fileName, Brush background)
        {
            ExportKerkythea(view, fileName, background, (int)view.ActualWidth, (int)view.ActualHeight);
        }

        /// <summary>
        /// Exports to kerkythea.
        /// </summary>
        /// <param name="view">
        /// The view.
        /// </param>
        /// <param name="fileName">
        /// Name of the file.
        /// </param>
        /// <param name="background">
        /// The background.
        /// </param>
        /// <param name="width">
        /// The width.
        /// </param>
        /// <param name="height">
        /// The height.
        /// </param>
        private static void ExportKerkythea(Viewport3D view, string fileName, Brush background, int width, int height)
        {
            //var scb = background as SolidColorBrush;
            //var backgroundColor = scb != null ? scb.Color : Colors.White;
            //var e = new KerkytheaExporter(fileName) { Width = width, Height = height, BackgroundColor = backgroundColor };
            //e.Export(view);
            //e.Close();
        }

        /// <summary>
        /// 导出obj格式文件
        /// </summary>
        /// <param name="view">3D视窗</param>
        /// <param name="fileName">文件名</param>
        private static void ExportObj(Viewport3D view, string fileName)
        {
            //var e = new ObjExporter(fileName);
            //e.Export(view);
            //e.Close();
        }

        /// <summary>
        /// 导出X3d文件
        /// </summary>
        /// <param name="view">3D视窗</param>
        /// <param name="fileName">文件名</param>
        private static void ExportX3d(Viewport3D view, string fileName)
        {
            //var e = new X3DExporter(fileName);
            //e.Export(view);
            //e.Close();
        }

        /// <summary>
        /// 导出COLLADA格式文件
        /// </summary>
        /// <param name="view">3D视窗</param>
        /// <param name="fileName">文件名</param>
        private static void ExportCollada(Viewport3D view, string fileName)
        {
            //var e = new ColladaExporter(fileName);
            //e.Export(view);
            //e.Close();
        }

        /// <summary>
        /// 导出xaml格式文件
        /// </summary>
        /// <param name="view">3D视窗</param>
        /// <param name="fileName">文件名</param>
        private static void ExportXaml(Viewport3D view, string fileName)
        {
            //var e = new XamlExporter(fileName);
            //e.Export(view);
            //e.Close();
        } 

        #endregion

        /// <summary>
        /// Gets the hit position transformed to global (viewport) coordinates.
        /// </summary>
        /// <param name="rayHit">
        /// The hit structure.
        /// </param>
        /// <param name="viewport">
        /// The viewport.
        /// </param>
        /// <returns>
        /// </returns>
        private static Point3D GetGlobalHitPosition(RayMeshGeometry3DHitTestResult rayHit, Viewport3D viewport)
        {
            var p = rayHit.PointHit;

            // first transform the Model3D hierarchy
            var t2 = GetTransform(rayHit.VisualHit, rayHit.ModelHit);
            if (t2 != null)
            {
                p = t2.Transform(p);
            }

            // then transform the Visual3D hierarchy up to the Viewport3D ancestor
            var t = GetTransform(viewport, rayHit.VisualHit);
            if (t != null)
            {
                p = t.Transform(p);
            }

            return p;
        }

        /// <summary>
        /// Gets the normal for a hit test result.
        /// </summary>
        /// <param name="rayHit">
        /// The ray hit.
        /// </param>
        /// <returns>
        /// </returns>
        private static Vector3D? GetNormalHit(RayMeshGeometry3DHitTestResult rayHit)
        {
            if ((rayHit.MeshHit.Normals == null) || (rayHit.MeshHit.Normals.Count < 1))
            {
                return null;
            }

            return rayHit.MeshHit.Normals[rayHit.VertexIndex1] * rayHit.VertexWeight1
                   + rayHit.MeshHit.Normals[rayHit.VertexIndex2] * rayHit.VertexWeight2
                   + rayHit.MeshHit.Normals[rayHit.VertexIndex3] * rayHit.VertexWeight3;
        }

        /// <summary>
        /// Gets the transform.
        /// </summary>
        /// <param name="current">
        /// The current.
        /// </param>
        /// <param name="model">
        /// The model.
        /// </param>
        /// <param name="parentTransform">
        /// The parent transform.
        /// </param>
        /// <returns>
        /// </returns>
        private static GeneralTransform3D GetTransform(Model3D current, Model3D model, Transform3D parentTransform)
        {
            var currentTransform = Transform3DHelper.CombineTransform(current.Transform, parentTransform);
            if (current == model)
            {
                return currentTransform;
            }

            var mg = current as Model3DGroup;
            if (mg != null)
            {
                foreach (var m in mg.Children)
                {
                    var result = GetTransform(m, model, currentTransform);
                    if (result != null)
                    {
                        return result;
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// 递归搜索给定类型的对象
        /// </summary>
        /// <param name="collection"></param>
        /// <param name="type"></param>
        /// <param name="output"></param>
        private static void SearchFor(IEnumerable<Visual3D> collection, Type type, ICollection<Model3D> output)
        {
            foreach (var visual in collection)
            {
                var modelVisual = visual as ModelVisual3D;
                if (modelVisual != null)
                {
                    var model = modelVisual.Content;
                    if (model != null)
                    {
                        if (type.IsAssignableFrom(model.GetType()))
                        {
                            output.Add(model);
                        }

                        // recursive
                        SearchFor(modelVisual.Children, type, output);
                    }

                    var modelGroup = model as Model3DGroup;
                    if (modelGroup != null)
                    {
                        SearchFor(modelGroup.Children, type, output);
                    }
                }
            }
        }
        
        /// <summary>
        /// 递归搜索给定类型的对象
        /// </summary>
        /// <param name="collection"></param>
        /// <param name="type"></param>
        /// <param name="output"></param>
        private static void SearchFor(IEnumerable<Model3D> collection, Type type, ICollection<Model3D> output)
        {
            foreach (var model in collection)
            {
                if (type.IsAssignableFrom(model.GetType()))
                {
                    output.Add(model);
                }

                var group = model as Model3DGroup;
                if (group != null)
                {
                    SearchFor(group.Children, type, output);
                }
            }
        }
        #endregion

        /// <summary>
        /// 碰撞结果类
        /// </summary>
        public class HitResult
        {
            #region 公开属性

            /// <summary>
            /// 获取或设置距离
            /// </summary>
            public double Distance { get; set; }

            /// <summary>
            /// 获取材质
            /// </summary>
            public MeshGeometry3D Mesh
            {
                get
                {
                    return this.RayHit.MeshHit;
                }
            }

            /// <summary>
            /// 获取3D模型
            /// </summary>
            public Model3D Model
            {
                get
                {
                    return this.RayHit.ModelHit;
                }
            }

            /// <summary>
            /// 
            /// </summary>
            public Vector3D Normal { get; set; }

            /// <summary>
            /// 获取或设置3D坐标
            /// </summary>
            public Point3D Position { get; set; }

            /// <summary>
            /// 
            /// </summary>
            public RayMeshGeometry3DHitTestResult RayHit { get; set; }

            /// <summary>
            /// 获取Visual3D对象
            /// </summary>
            public Visual3D Visual
            {
                get
                {
                    return this.RayHit.VisualHit;
                }
            }

            #endregion
        }
    }
}
